package com.inspectortime.domain.impl;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;

import com.inspectortime.common.PersistentEntityBase;
import com.inspectortime.domain.User;
import com.inspectortime.domain.type.Name;
import com.inspectortime.timestamp.Timestamped;

@Entity
@Table(name = "USER")
@Inheritance(strategy = InheritanceType.JOINED)
@org.hibernate.annotations.Proxy(lazy = false)
public abstract class UserImpl extends PersistentEntityBase implements User, Timestamped {

	private static final String PASSWORD_ENCODING_SECRET = "a841ba754cc57542bcb62c52788271fe";
	
	private String passwordEncodingUniqueId;

	private Date createdTime;
	private Date lastModifiedTime;

	private Name name;
	private String email;
	
	@Transient
	private String oldEmail;	

	@Transient
	private String emailConfirm;

	private String passwordResetToken;
	private String userName;
	
	@Transient
	private String oldUserName;

	@Deprecated
	@Transient
	private String password;
	private String encodedPassword;
	
	
	public UserImpl() {
	}

	public UserImpl(Name name) {
		this();
		this.name = name;
	}
	
	public Date getCreatedTime() {
		return createdTime;
	}

	public void setCreatedTime(Date createdTime) {
		this.createdTime = createdTime;
	}

	public Date getLastModifiedTime() {
		return lastModifiedTime;
	}

	public void setLastModifiedTime(Date lastModifiedTime) {
		this.lastModifiedTime = lastModifiedTime;
	}
	
	/**
	 * Used only for the purpose of providing a unique "salt" for the password
	 * encoding mechanism
	 * 
	 * @return
	 */
	@Column(length = 16)
	public String getPasswordEncodingUniqueId() {
		return passwordEncodingUniqueId;
	}

	public void setPasswordEncodingUniqueId(String passwordEncodingUniqueId) {
		this.passwordEncodingUniqueId = passwordEncodingUniqueId;
	}

	@Column(length = 100)
	public String getEmail() {
		return email;
	}

	@Transient
	public String getOldEmail() {
		return oldEmail;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public void setOldEmail(String email) {
		this.oldEmail = email;
	}

	@Transient
	public String getEmailConfirm() {
		return emailConfirm;
	}

	public void setEmailConfirm(String emailConfirm) {
		this.emailConfirm = emailConfirm;
	}

	@Column(length = 50)
	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	@Transient
	public String getOldUserName() {
		return oldUserName;
	}

	public void setOldUserName(String userName) {
		this.oldUserName = userName;
	}

	@Column(length = 50)
	@Deprecated
	@Transient
	public String getPassword() {
		return password;
	}

	@Deprecated
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Encodes cleartext password and compares it with the encoded one
	 * 
	 * @param cleartextPassword
	 * @return true if match, false otherwise
	 */
	public boolean passwordMatches(String cleartextPassword) {
		if (StringUtils.isBlank(this.encodedPassword)) {
			return false;
		}
		String encodedCleartextPassword = this.encode(cleartextPassword);
		return encodedPassword.equals(encodedCleartextPassword);
	}

	/**
	 * Sets the encodedPassword by encoding the given cleartextPassword
	 * 
	 * @param cleartextPassword
	 */
	public void setCleartextPassword(String cleartextPassword) {
		this.encodedPassword = encode(cleartextPassword);
	}

	/**
	 * Encodes password. Will lazily create a "salt" code if none found which is
	 * unique to this user id.
	 * 
	 * @param password
	 *            the cleartext password
	 * @return the encoded password as a String
	 */
	public String encode(String password) {
		if (StringUtils.isBlank(this.passwordEncodingUniqueId)) {
			passwordEncodingUniqueId = RandomStringUtils.randomAlphanumeric(16);
		}
		StringBuffer sb = new StringBuffer();
		sb.append(passwordEncodingUniqueId);
		sb.append(PASSWORD_ENCODING_SECRET);
		sb.append(password);
		MessageDigest md = null;
		try {
			md = MessageDigest.getInstance("SHA-1");
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(
					"Password encoding algorithm SHA-1 not found, please check you Java version",
					e);
		}
		try {
			md.update(sb.toString().getBytes("UTF-8"));
			byte[] hash = md.digest();
			Base64 base64Encoder = new Base64();
			byte[] base64EncodedHash = base64Encoder.encode(hash);
			return new String(base64EncodedHash, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(
					"UTF-8 not supported, please check you Java version", e);
		}
	}

	/**
	 * Sets the encoded password (used hibernate populates this object from a
	 * data store)
	 * 
	 * @param encodedPassword
	 *            the encoded password
	 */
	public void setEncodedPassword(String encodedPassword) {
		this.encodedPassword = encodedPassword;
	}

	/**
	 * Gets the encoded password. Normally not used for anything except keeping
	 * it around for when user data is re-saved.
	 * 
	 * @return encoded password as a String
	 */
	@Column(length = 28)
	public String getEncodedPassword() {
		return this.encodedPassword;
	}

	@Column(length = 32)
	public String getPasswordResetToken() {
		return this.passwordResetToken;
	}

	public void setPasswordResetToken(String token) {
		this.passwordResetToken = token;
	}

	public Name getName() {
		return name;
	}

	public void setName(Name name) {
		this.name = name;
	}
	
	
}
